#include <stdlib.h>
#include <gtk/gtk.h>
#include <pthread.h>
#include <nfc/nfc.h>
//#include "internal.h"
#include "nfc_management.h"
#include "locker.h"
#include "key_generator.h"
#include "database.h"
#include <wiringPi.h>
#include <string.h>

pthread_t backend_thread;
pthread_mutex_t lock;
int thread_count = 0;
//bool locker_active = true;

//typedef struct locker *locker_t;
//typedef struct mifare_tag *MifareTag;

//GtkWidget *lockerList;
//struct locker *lockers = NULL;

struct winListLockers {
    GtkWidget* window;
    GtkWidget* list;
    struct locker* lockers;
};

void *main_locker(void *param)
{
    //thread_count++;
    struct locker *Locker = param;

    nfc_device *device = NULL;
    MifareTag *tags = NULL;
    nfc_target *current_tag = NULL;

    //nfc_context *context;
    //nfc_init (&context);

    device = Locker->reader;

    if (!device)
    {
        printf("nfc_open() failed.\n");
        //continue;
    }

    while(Locker->active)
    //while(locker_active)
    {
        //printf("Locker->active = %d\n", Locker->active);
        // look for all the tags connected to the current device
		if (!(tags = freefare_get_tags (device)));

		if (tags == NULL)
			printf("Error listing tags. -1\n");
		if (!tags)
		{
			//printf("No tag found\n");
			continue;
		}

		// look through all the tags
//		printf ("Looking at all the tags on reader #%d\n", d);
		int i;
		for (i = 0; tags[i]; i++)
		{
			printf ("Before  continue\n");
			enum mifare_tag_type tagType;
			tagType = freefare_get_tag_type (tags[i]);
			printf("tagType = %d\n", tagType);
			//if (DESFIRE != freefare_get_tag_type (tags[i]))
            //    continue;

            switch (tagType)
            {
                case DESFIRE:
                    break;
                case CLASSIC_1K:
                    break;
                case ULTRALIGHT:
                    continue;
                default:
                    continue;
            }

			printf ("After  continue\n");

			char *tag_uid = freefare_get_tag_uid (tags[i]);
			//char buffer[BUFSIZ];

			printf ("Found %s with UID %s. ", freefare_get_tag_friendly_name (tags[i]), tag_uid);

			int res;
			uint8_t ret_rx_connect[CMD_SELECT_AID_SIZE_RX];
			int j;
			//BUFFER_INIT (ret, CMD_SELECT_AID_SIZE_RX);

			for (j = 0; j < sizeof(ret_rx_connect); j++)
				ret_rx_connect[j] = 0;

			res = mifare_desfire_connect (tags[i], &ret_rx_connect[0], current_tag);

			//for (j = 0; j < sizeof(ret_rx); j++)
			//	printf("ret_rx[%d] = %x\n", j, ret_rx[j]);
			if (res < 0) {
				printf ("Can't connect to Mifare DESFire target.\n");
				break;
			}

			// check the message for the OK result
			if ((ret_rx_connect[CMD_SELECT_AID_SIZE_RX - 2] != 0x90) && (ret_rx_connect[CMD_SELECT_AID_SIZE_RX - 1] != 0x00))
			{
			    printf ("Did not get correct result from NFC tag\n");
                break;
			}

            // check if the key is empty
			bool is_key_empty = true;
			char key_rx[KEY_SIZE]; // key received from the connect
			char *key_tx = NULL; // key to send to the "tag"
			for (j = 0; j < KEY_SIZE; j++)
            {
                key_rx[j] = ret_rx_connect[j];
                if (ret_rx_connect[j] != EMPTY_KEY[j])
                {
                    is_key_empty = false;
                }
            }

			// check locker state
			if (Locker->locked)
			{
                if (is_key_empty)
                {
                    printf("ERROR 1 - Locker locked, key empty\n");
                    res = desfire_send_cmd(tags[i], ERROR1, NULL);
                    blink_red_led(Locker, ERROR_RED_LED_BLINK_RATE, ERROR_RED_LED_BLINK_CYCLES);
                    if (res < 0)
                    {
                        printf ("Cannot send command to phone\n");
                        break;
                    }
                }
                else if (compare_key(&key_rx[0], Locker) == 0) // keys are equal
                {
                    printf("Locker Locked, key matched\n");
                    res = desfire_send_cmd(tags[i], DELETE_KEY, NULL);
                    if (res < 0)
                    {
                        printf ("Cannot send command to phone\n");
                        break;
                    }
                    else
                    {
                        printf("Locker is now unlocked\n");
                        unlock_locker(Locker);
                        update_usage_stats(0, Locker->lockerNum);
                        delete_key_values(Locker->lockerNum);
                    }
                }
                else
                {
                    printf("ERROR 0 - Locker locked, key not matched\n");
                    res = desfire_send_cmd(tags[i], ERROR0, NULL);
                    blink_red_led(Locker, ERROR_RED_LED_BLINK_RATE, ERROR_RED_LED_BLINK_CYCLES);
                    if (res < 0)
                    {
                        printf ("Cannot send command to phone\n");
                        break;
                    }
                }
			}
            else
            {
                if (is_key_empty)
                {
                    printf("Locker unlocked, key empty\n");

                    key_tx = malloc(KEY_SIZE * sizeof(char));

                    printf("Generating key\n");
                    generate_key(key_tx, KEY_SIZE);

                    for (j = 0; j < KEY_SIZE; j++)
                        printf("key_tx[%d] = %x\n", j, key_tx[j]);

                    printf("About to send command\n");
                    res = desfire_send_cmd(tags[i], SEND_KEY, key_tx);
                    if (res < 0)
                    {
                        printf ("Cannot send key to phone\n");
                        erase_key(Locker);
                        break;
                    }
                    else
                    {
                        printf("Locker is now locked\n");
                        lock_locker(Locker, key_tx);
                        printf("lock Locker\n");
                        insert_usage_stats(Locker->lockerNum);
                        printf("usage stats\n");
                        insert_key_values(Locker->lockerNum, key_tx);
                        printf("keyValues\n");
                        //change_locker_state(Locker);
                    }
                }
                else
                {
                    printf("ERROR 0 - Locker unlocked, key not empty\n");
                    res = desfire_send_cmd(tags[i], ERROR0, NULL);
                    blink_red_led(Locker, ERROR_RED_LED_BLINK_RATE, ERROR_RED_LED_BLINK_CYCLES);
                    if (res < 0)
                    {
                        printf ("Cannot send command to phone\n");
                        break;
                    }
                }
            }
			// stall thread while we  wait for the tag to move away from the reader
			while (!nfc_initiator_target_is_present(device, current_tag) && Locker->active);
			//while (!nfc_initiator_target_is_present(device, current_tag) && locker_active);

			mifare_desfire_disconnect (tags[i]);

			free (tag_uid);
		}
    }

    // cleanup resources before thread closes
    // todo: change this to free individual tags
    //freefare_free_tags (tags);
    printf("Cleaning up resources from thread\n");
    nfc_close (Locker->reader);
    nfc_exit (Locker->context);

    pthread_mutex_lock(&lock);
    thread_count--;
    pthread_mutex_unlock(&lock);

    pthread_exit(NULL);
}

struct locker *initialize_backend(GtkWidget* list)
{
    struct locker *lockers = NULL;
    printf("1 address of lockers = %x\n", &lockers);
    if (pthread_mutex_init(&lock, NULL) != 0)
    {
        printf("\n  Mutex Init Failed\n");
        return NULL;
    }

    nfc_connstring devices[MAX_NUM_LOCKERS];
    size_t device_count;

    nfc_context *context;
    nfc_init (&context);
    if (context == NULL)
    {
        printf("Unable to init libnfc (malloc)");
        return NULL;
    }

    device_count = nfc_list_devices (context, devices, 8);
    if (device_count <= 0)
	{
		printf("No NFC device found.\n");
		return NULL;
	}
	else { //the following for loop adds objects to the list box for the UI
	    int i;
        for(i = 0; i < device_count; i++) {
            char lockerNum[1];
            sprintf(lockerNum,"%d",i);
            printf("found locker %d\n",i);
            createLocker(list,lockerNum,i);
        }
	}

    lockers = malloc(device_count * sizeof(struct locker));
    int i;
    //for (i = 0; i < device_count; i++)
    //    lockers[i] = NULL;

    //for (i = 0; i < device_count; i++)
    //{
    //    &lockers[i] = malloc(sizeof(struct locker));
    //}

    //lockers = malloc(device_count * sizeof(struct locker));
    printf("Initializing lockers\n");
    init_lockers(lockers, device_count, devices, context);
    printf("lockers[0].active = %d\n", lockers[0].active);
    pthread_t locker_threads[device_count];

    for (i = 0; i < device_count; i++)
    {
         pthread_create(&locker_threads[i], NULL, main_locker, &lockers[i]);
         thread_count++;
    }

    //printf("2 address of lockers = %x\n", &lockers);
    return &lockers[0];
}

void cleanup_lockers(struct locker *lockers)
{
    int i;
    for (i = 0; i < total_num_lockers; i++)
    {
        printf("Setting locker %d to inactive\n", i);
        //printf("Locker %d active = %d\n", i, (&lockers[i]).active);
        (lockers[i]).active = false;
        //locker_active = false;
        printf("Locker %d is now inactive\n", i);
    }

    // wait for the threads to finish
    printf("Waiting for threads to finish\n");
    while(thread_count);
    printf("Threads closed\n");

    for (i = 0; i < total_num_lockers; i++)
    {
        free_lockers_mem(lockers, total_num_lockers);
    }

    free(lockers);
}

static void unlockAll (GtkWidget *wid, struct winListLockers *data)
{
  GtkWidget *dialog = NULL;

  int i;

  GList * lstLockers = gtk_container_get_children(data->list);
  guint numLockers = g_list_length(lstLockers);
  for(i = 0; i < numLockers; i++)
  {
    unlock_locker(&data->lockers[i]);
    update_usage_stats(1, i);
    delete_key_values(i);
  }

  dialog = gtk_message_dialog_new (GTK_WINDOW (data->window), GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "All %d lockers have been unlocked!", numLockers);
  gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);
}

static void unlock (GtkWidget *wid, struct winListLockers *data)
{
  GtkWidget *dialog = NULL;
  GtkListBoxRow * selectedRow = NULL;
  GtkWidget *child = NULL;
  selectedRow = gtk_list_box_get_selected_row(data->list);

  if(selectedRow == NULL){
    dialog = gtk_message_dialog_new (GTK_WINDOW (data->window), GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "Please select a locker!");
    gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
    gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);
  }

  else{
    child = gtk_bin_get_child(selectedRow);
    const gchar* name = gtk_widget_get_name(child);
    const gchar* lblString = "GtkLabel";
    if(!g_ascii_strcasecmp(name,lblString)){
        const gchar* lblLocker = gtk_label_get_text(child);
        const gchar* tempStr = " has been unlocked";
        const gchar* strLocker = "locker ";
        const int lockernum = atoi(lblLocker);

        GList* tempList = NULL;
        tempList = g_list_append(tempList,tempStr);
        GList* tempList1 = NULL;
        tempList1 = g_list_append(tempList1,strLocker);
        gchar* text = g_strconcat((gchar*) tempList1->data,lblLocker, (gchar*) tempList->data,NULL);

        unlock_locker(&data->lockers[lockernum]);
        update_usage_stats(1, lockernum);
        delete_key_values(lockernum);

        dialog = gtk_message_dialog_new (GTK_WINDOW (data->window), GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, text);
        gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
        gtk_dialog_run (GTK_DIALOG (dialog));
        gtk_widget_destroy (dialog);
    }

    else{
        dialog = gtk_message_dialog_new (GTK_WINDOW (data->window), GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "Not a GtkLabel in GtkListBox");
        gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
        gtk_dialog_run (GTK_DIALOG (dialog));
        gtk_widget_destroy (dialog);
    }
  }
}

void createLocker (GtkWidget* list, char *lblText, int position)
{
  GtkWidget *label = NULL;
  label = gtk_label_new(lblText);
  gtk_widget_set_can_focus(label,true);
  GdkColor white = {0,0xffff,0,0};
    gtk_widget_override_background_color(label,GTK_STATE_FLAG_NORMAL,&white);
  gtk_widget_set_size_request(label, 1400, 40);
  gtk_list_box_insert(list,label,position);

}

gboolean filter_func (GtkListBoxRow *row, gpointer data)
{
  gchar *txtFilter = data;
  gint *len = strlen(txtFilter);
  GtkWidget *child;
  child = gtk_bin_get_child(row);
  const gchar* name = gtk_widget_get_name(child);
  const gchar* lblLocker = gtk_label_get_text(child);
  return !g_ascii_strncasecmp(lblLocker,txtFilter,len);
}

void filter(GtkEditable* edit, GtkWidget* list) {
  GtkListBoxRow * currentRow = NULL;
  GtkWidget *child = NULL;
  gint i = 0;

  const gchar* content = gtk_entry_get_text( GTK_ENTRY(edit) );
  gtk_list_box_set_filter_func (list, filter_func, content, NULL);
}

void name_insert_after (GtkEditable* edit,
                   gchar* new_text,
                   gint new_length,
                   gpointer position,
                   gpointer data)
{
  GtkWidget* list = (gpointer*)data;
  filter(edit, list);
}

void name_delete_after (GtkEditable* edit,
                   gint start_pos,
                   gint end_pos,
                   gpointer data)
{
  GtkWidget* list = (gpointer*)data;
  filter(edit, list);
}

void row_selected(GtkListBox * list,
                  GtkListBoxRow * row,
                  gpointer data) {
    gtk_widget_grab_focus(row);

}

int main (int argc, char *argv[])
{
    GtkWidget *win = NULL;
    GtkWidget *fixed = NULL;
    GtkWidget *btnUnlockAll = NULL;
    GtkWidget *btnUnlock = NULL;
    GtkWidget *btnClose = NULL;
    GtkWidget *lockerFilter = NULL;
    GtkWidget * lockerList = NULL;
    struct locker *lockers = NULL;

    printf("beginning of main\n");

    lockerList = gtk_list_box_new();

    //locker_t lockers = NULL;
    //locker_t *lockers = NULL;
    printf("made listBox\n");
    wiringPiSetup();
    //wiringPiSetupGpio();
    printf("Initializing backend\n");
    lockers = initialize_backend(lockerList);
    //printf("3 address of lockers = %x\n", &lockers);
    printf("lockers[0].active = %d\n", lockers[0].active);

    if (lockers == NULL)
    {
        printf("Unable to initialize the backend\n");
        return -1;
    }

    /* Initialize GTK+ */
    g_log_set_handler ("Gtk", G_LOG_LEVEL_WARNING, (GLogFunc) gtk_false, NULL);
    gtk_init (&argc, &argv);
    g_log_set_handler ("Gtk", G_LOG_LEVEL_WARNING, g_log_default_handler, NULL);

    /* Create the main window */
    win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_container_set_border_width (GTK_CONTAINER (win), 8);
    gtk_window_set_title (GTK_WINDOW (win), "test");
    gtk_window_fullscreen(win);
    gtk_widget_realize (win);
    long width;
    long height;
    //gtk_window_get_size(win,width, height);
    g_signal_connect (win, "destroy", gtk_main_quit, NULL);

    fixed = gtk_fixed_new();
    gtk_container_add(GTK_CONTAINER(win), fixed);

    lockerFilter = gtk_entry_new();
    gtk_entry_set_text(lockerFilter , "Search for locker");
    g_signal_connect_after( lockerFilter, "insert-text", G_CALLBACK(name_insert_after), (gpointer) lockerList);
    g_signal_connect_after( lockerFilter, "delete-text", G_CALLBACK(name_delete_after), (gpointer) lockerList);
    gtk_fixed_put(GTK_FIXED(fixed), lockerFilter, 150, 50);
    gtk_widget_set_size_request(lockerFilter, 1470, 50);

    GdkRGBA white = {0xffff,0xffff,0xffff,0xffff};
    gtk_widget_override_background_color(lockerList,GTK_STATE_FLAG_NORMAL,&white);
    gtk_widget_override_background_color(lockerList,GTK_STATE_FLAG_ACTIVE,&white);
    g_signal_connect (G_OBJECT (lockerList), "row-selected", G_CALLBACK (row_selected), (gpointer) lockerFilter);
    GtkListBoxRow * row = gtk_list_box_get_row_at_index(lockerList, 0);
    gtk_list_box_select_row(lockerList,row);
    gtk_fixed_put(GTK_FIXED(fixed), lockerList, 150, 150);
    gtk_widget_set_size_request(lockerList, 1470, 550);

    struct winListLockers * data = malloc(sizeof(struct winListLockers));
    data->window =win;
    data->list = lockerList;
    data->lockers= lockers;

    btnUnlock = gtk_button_new_with_label("Unlock");
    g_signal_connect (G_OBJECT (btnUnlock), "clicked", G_CALLBACK (unlock), data);
    gtk_fixed_put(GTK_FIXED(fixed), btnUnlock, 150, 750);
    gtk_widget_set_size_request(btnUnlock, 480, 75);

    btnUnlockAll = gtk_button_new_with_label("Unlock All Lockers");
    g_signal_connect (G_OBJECT (btnUnlockAll), "clicked", G_CALLBACK (unlockAll), data);
    gtk_fixed_put(GTK_FIXED(fixed), btnUnlockAll, 644, 750);
    gtk_widget_set_size_request(btnUnlockAll, 480, 75);

    btnClose = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
    g_signal_connect (btnClose, "clicked", gtk_main_quit, NULL);
    gtk_fixed_put(GTK_FIXED(fixed), btnClose, 1139, 750);
    gtk_widget_set_size_request(btnClose, 480, 75);

    //int x = 0;
    //pthread_create(&backend_thread, NULL, main_backend, &x);

    /* Enter the main loop */
    gtk_widget_show_all (win);
    gtk_main ();

    printf("Menu closed\n");

    printf("Cleaning up program...\n");
    cleanup_lockers(lockers);
    printf("Finished cleaning program\n");

    printf("Finished running program\n");
    return 0;
}
